/******************************************************************************
* 	Copyright (c) 2007-2008 All rights reserved
*		Asemantics S.r.l
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer. 
*
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in
*    the documentation and/or other materials provided with the
*    distribution.
*
* 3. The end-user documentation included with the redistribution,
*    if any, must include the following acknowledgment:
*       "This product includes software developed by 
*	 Asemantics S.r.l."
*    Alternately, this acknowledgment may appear in the software itself,
*    if and wherever such third-party acknowledgments normally appear.
*
* 4. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*    This product includes software developed by Asemantics S.r.l.
*    the Semantic Web company, Rome, London, Leiden and its contributors. 
*
* 5. Neither the name of the company nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Author: Andrea Marchesini - baku@asemantics.com
*
******************************************************************************/

#include "config.h"

#include <controller.h>
#include <configure.h>
#include <network.h>
#include <http.h>
#include <log.h>

/* NETWORK: */
#ifdef G_OS_UNIX
#  include <netinet/in.h>
#  include <sys/socket.h>
#  include <netdb.h>
#  include <signal.h>
#endif

static GIOChannel *network_create (TBGlobal * data, GError ** error);
static GIOChannel *network_create6 (TBGlobal * data, GError ** error);

static gpointer network_thread (TBGlobal * data);

static gboolean network_read (GIOChannel *, GIOCondition, TBGlobal *);
static gboolean network_read_timeout (TBGlobal * data);

static void network_client_close (TBNetworkClient * client);
static void network_client_free (TBNetworkClient * client);
static gboolean network_client_timeout (TBNetworkClient * client);
static gboolean network_client_read (GIOChannel *, GIOCondition,
				     TBNetworkClient * client);
static gboolean network_client_read_timeout (TBNetworkClient * client);
static gboolean network_client_write (GIOChannel *, GIOCondition,
				      TBNetworkClient * client);
static gboolean network_client_write_timeout (TBNetworkClient * client);

static gboolean network_client_send (TBNetworkClient * client, gchar * header,
				     gchar * buffer, gsize buffersize);
static void network_client_switch (TBNetworkClient * client);
static void network_client_error (TBNetworkClient * client,
				  TBHttpError error);

static gboolean network_client_header (TBNetworkClient * client,
				       TBHttpError * error);

/* INITIALIZE ***************************************************************/

/* Generic init: */
gboolean
network_init (TBGlobal * data, GError ** error)
{
  if (data->net_ipv6 == FALSE)
    {
      if (!(data->net_socket = network_create (data, error)))
	return FALSE;
    }

  else if (!(data->net_socket = network_create6 (data, error)))
    return FALSE;

  if (!
      (data->net_thread =
       g_thread_create ((GThreadFunc) network_thread, data, TRUE, error)))
    {
      network_close (data);
      return FALSE;
    }

  return TRUE;
}

/* The thread for the net: */
static gpointer
network_thread (TBGlobal * data)
{
#ifdef G_OS_UNIX
  signal (SIGPIPE, SIG_IGN);
#endif

  /* The context: */
  data->net_context = g_main_context_new ();
  data->net_loop = g_main_loop_new (data->net_context, FALSE);

  /* The callback for the socket input: */
  data->net_socket_source = g_io_create_watch (data->net_socket, G_IO_IN);
  g_source_set_callback (data->net_socket_source, (GSourceFunc) network_read,
			 data, NULL);
  g_source_attach (data->net_socket_source, data->net_context);

  /* loop: */
  g_main_loop_run (data->net_loop);

  g_source_unref (data->net_socket_source);

  g_main_loop_unref (data->net_loop);
  g_main_context_unref (data->net_context);

  g_thread_exit (NULL);
  return NULL;
}

/* Close the socket */
void
network_close (TBGlobal * data)
{
  g_main_loop_quit (data->net_loop);

  g_io_channel_shutdown (data->net_socket, FALSE, NULL);
  g_io_channel_unref (data->net_socket);

  while (data->net_clients)
    network_client_close (data->net_clients->data);

  g_thread_join (data->net_thread);
}

/* IPV6 socket: */
static GIOChannel *
network_create6 (TBGlobal * data, GError ** error)
{
  gint fd, yes = 1;
  GIOChannel *channel;
  struct sockaddr_in6 sock;

  if ((fd = socket (AF_INET6, SOCK_STREAM, 0)) < 0)
    {
      g_set_error (error, controller_error_quark (), 0,
		   "Error opening the socket: %s\n", g_strerror (errno));
      return NULL;
    }

  /* Attivo la chiusura legata al processo: */
  if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (int)))
    {
      close (fd);
      g_set_error (error, controller_error_quark (), 0,
		   "Error setting options on the socket: %s\n",
		   g_strerror (errno));
      return NULL;
    }

  /* Inizializzo la struttura dati e la porta: */
  memset (&sock, 0, sizeof (struct sockaddr_in6));
  sock.sin6_family = AF_INET6;
  sock.sin6_port = htons (data->net_port);

  /* Inizializzo l'interfaccia di bind: */
  if (!data->net_interface)
    {
      sock.sin6_addr = in6addr_any;
    }

  else
    {
      struct hostent *hp;

      /* Procedo con la risoluzione dell'interfaccia: */
      if (!(hp = gethostbyname2 (data->net_interface, AF_INET6)))
	{
	  close (fd);
	  g_set_error (error, controller_error_quark (), 0,
		       "Error resolving the interface '%s': %s\n",
		       data->net_interface, g_strerror (errno));
	  return NULL;
	}

      sock.sin6_family = hp->h_addrtype;
      memcpy (&sock.sin6_addr, hp->h_addr, hp->h_length);
    }

  /* Attivo il bind: */
  if (bind (fd, (struct sockaddr *) &sock, sizeof (struct sockaddr_in6)) < 0)
    {
      close (fd);
      g_set_error (error, controller_error_quark (), 0,
		   "Error binding the socket: %s\n", g_strerror (errno));
      return NULL;
    }

  /* Configuro il numero di connessioni simultanee: */
  if (listen (fd, data->net_listen) < 0)
    {
      close (fd);
      g_set_error (error, controller_error_quark (), 0,
		   "Error listeining on the socket: %s\n",
		   g_strerror (errno));
      return NULL;
    }

  /* Creo il channel dato un fd: */
  channel = g_io_channel_unix_new (fd);

  /* Setto flag e encoding per il nuovo channel: */
  g_io_channel_set_encoding (channel, NULL, NULL);
  g_io_channel_set_flags (channel,
			  g_io_channel_get_flags (channel) |
			  G_IO_FLAG_NONBLOCK, NULL);

  return channel;
}

/* IPv4 socket: */
static GIOChannel *
network_create (TBGlobal * data, GError ** error)
{
  gint fd, yes = 1;
  struct sockaddr_in sock;
  GIOChannel *channel;

  /* Richiesta di un socket: */
  if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
    {
      g_set_error (error, controller_error_quark (), 0,
		   "Error creating the socket: %s", g_strerror (errno));
      return NULL;
    }

  /* Attivo la chiusura legata al processo: */
  if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (int)))
    {
      close (fd);
      g_set_error (error, controller_error_quark (), 0,
		   "Error setting options on the socket: %s\n",
		   g_strerror (errno));
      return NULL;
    }

  /* Inizializzo la struttura dati e la porta: */
  memset (&sock, 0, sizeof (struct sockaddr_in));
  sock.sin_family = AF_INET;
  sock.sin_port = htons (data->net_port);

  /* Inizializzo l'interfaccia di bind: */
  if (!data->net_interface)
    sock.sin_addr.s_addr = htonl (INADDR_ANY);

  else
    {
      struct hostent *hp;

      /* Procedo con la risoluzione dell'interfaccia: */
      if (!(hp = gethostbyname (data->net_interface)))
	{
	  close (fd);
	  g_set_error (error, controller_error_quark (), 0,
		       "Error resolving the interface '%s': %s",
		       data->net_interface, g_strerror (errno));
	  return NULL;
	}

      sock.sin_family = hp->h_addrtype;
      memcpy (&sock.sin_addr, hp->h_addr, hp->h_length);
    }

  /* Attivo il bind: */
  if (bind (fd, (struct sockaddr *) &sock, sizeof (struct sockaddr_in)) < 0)
    {
      close (fd);
      g_set_error (error, controller_error_quark (), 0,
		   "Error binding the socket: %s\n", g_strerror (errno));
      return NULL;
    }

  /* Configuro il numero di connessioni simultanee: */
  if (listen (fd, data->net_listen) < 0)
    {
      close (fd);
      g_set_error (error, controller_error_quark (), 0,
		   "Error listeining on the socket: %s\n",
		   g_strerror (errno));
      return NULL;
    }

  /* Creo il channel dato un fd: */
  channel = g_io_channel_unix_new (fd);

  /* Setto flag e encoding per il nuovo channel: */
  g_io_channel_set_encoding (channel, NULL, NULL);
  g_io_channel_set_flags (channel,
			  g_io_channel_get_flags (channel) |
			  G_IO_FLAG_NONBLOCK, NULL);

  return channel;
}

/* Reading function: */
static gboolean
network_read (GIOChannel * source, GIOCondition cond, TBGlobal * data)
{
  struct sockaddr_in sock;
  struct sockaddr_in6 sock6;

  TBNetworkClient *client;

  gchar *ip;
  gint fd;

  /* IPv6: */
  if (data->net_ipv6 == TRUE)
    {
      socklen_t size = sizeof (struct sockaddr_in6);
      gint id;
      GString *str;

      /* IPv6 accept: */
      if ((fd =
	   accept (g_io_channel_unix_get_fd (source),
		   (struct sockaddr *) &sock6, &size)) < 0)
	{
	  g_source_destroy (data->net_socket_source);

	  data->net_socket_source = g_timeout_source_new (200);
	  g_source_set_callback (data->net_socket_source,
				 (GSourceFunc) network_read_timeout, data,
				 NULL);
	  g_source_attach (data->net_socket_source, data->net_context);

	  return FALSE;
	}

      /* MaxClients check: */
      if (data->net_maxclients
	  && data->net_clients_numb >= data->net_maxclients)
	{
	  log_write_generic (data, LOG_VERBOSE_WARNING,
			     LOG_NETWORK_CLIENT_ERROR, "error",
			     LOG_VERBOSE_WARNING, LOG_TYPE_STRING,
			     "Too much clients", NULL);
	  close (fd);

	  return TRUE;
	}

      /* Log information: */
      str = g_string_new (NULL);

      for (id = 0; id < 16; id++)
	{
	  if (sock6.sin6_addr.s6_addr[id])
	    g_string_append_printf (str, "%X%c", sock6.sin6_addr.s6_addr[id],
				    id < 15 ? ':' : ' ');
	  else
	    g_string_append_printf (str, "%c", id < 15 ? ':' : ' ');
	}

      ip = g_string_free (str, FALSE);

      log_write_generic (data, LOG_VERBOSE_INFO, LOG_NETWORK_CLIENT_CONNECT,
			 "client", LOG_VERBOSE_INFO, LOG_TYPE_STRING, ip,
			 NULL);
    }

  /* IPv4: */
  else
    {
      socklen_t size = sizeof (struct sockaddr_in);
      gint addr;

      /* IPv4 accept: */
      if ((fd =
	   accept (g_io_channel_unix_get_fd (source),
		   (struct sockaddr *) &sock, &size)) < 0)
	{
	  g_source_destroy (data->net_socket_source);

	  data->net_socket_source = g_timeout_source_new (200);
	  g_source_set_callback (data->net_socket_source,
				 (GSourceFunc) network_read_timeout, data,
				 NULL);
	  g_source_attach (data->net_socket_source, data->net_context);

	  return FALSE;
	}

      addr = ntohl (sock.sin_addr.s_addr);

      ip =
	g_strdup_printf ("%d.%d.%d.%d", (unsigned int) addr >> 24,
			 (unsigned int) (addr >> 16) % 256,
			 (unsigned int) (addr >> 8) % 256,
			 (unsigned int) addr % 256);

      log_write_generic (data, LOG_VERBOSE_INFO, LOG_NETWORK_CLIENT_CONNECT,
			 "new_client", LOG_VERBOSE_INFO, LOG_TYPE_STRING, ip,
			 NULL);
    }

  /* Creo l'item da far gestire a qualche thread: */
  client = g_malloc0 (sizeof (TBNetworkClient));

  client->ip = ip;
  client->data = data;
  client->channel = g_io_channel_unix_new (fd);

  /* Setto flag e encoding per il nuovo channel: */
  g_io_channel_set_encoding (client->channel, NULL, NULL);
  g_io_channel_set_flags (client->channel,
			  g_io_channel_get_flags (client->
						  channel) |
			  G_IO_FLAG_NONBLOCK, NULL);

  data->net_clients = g_list_append (data->net_clients, client);
  data->net_clients_numb++;

  /* Attivo il timer per la chiusura del socket causa non arrivo dati: */
  client->timeout_source = g_timeout_source_new (data->net_timeout * 1000);
  g_source_set_callback (client->timeout_source,
			 (GSourceFunc) network_client_timeout, client, NULL);
  g_source_attach (client->timeout_source, data->net_context);

  /* Attivo la chiamata per l'arrivo di dati: */
  client->channel_source = g_io_create_watch (client->channel, G_IO_IN);
  g_source_set_callback (client->channel_source,
			 (GSourceFunc) network_client_read, client, NULL);
  g_source_attach (client->channel_source, data->net_context);

  return TRUE;
}

static gboolean
network_read_timeout (TBGlobal * data)
{
  g_source_destroy (data->net_socket_source);

  data->net_socket_source = g_io_create_watch (data->net_socket, G_IO_IN);
  g_source_set_callback (data->net_socket_source, (GSourceFunc) network_read,
			 data, NULL);
  g_source_attach (data->net_socket_source, data->net_context);

  return FALSE;
}

/* After X seconds, the client will be closed: */
static gboolean
network_client_timeout (TBNetworkClient * client)
{
  network_client_close (client);
  return FALSE;
}

/* CLIENT READ **************************************************************/

/* This function reads something from the client: */
static gboolean
network_client_read (GIOChannel * source, GIOCondition cond,
		     TBNetworkClient * client)
{
  gchar *line = NULL;
  gsize size, last;
  GIOStatus status;

  status = g_io_channel_read_line (source, &line, &size, &last, NULL);

  /* The status of the read: */
  switch (status)
    {
    case G_IO_STATUS_NORMAL:
      break;

      /* Setting a delay: */
    case G_IO_STATUS_AGAIN:
      g_source_destroy (client->channel_source);

      client->channel_source = g_timeout_source_new (200);
      g_source_set_callback (client->channel_source,
			     (GSourceFunc) network_client_read_timeout,
			     client, NULL);
      g_source_attach (client->channel_source, client->data->net_context);
      return FALSE;

      /* Close the socket: */
    case G_IO_STATUS_ERROR:
    case G_IO_STATUS_EOF:
      network_client_close (client);
      return FALSE;
    }

  /* Removing the timeout: */
  if (client->timeout_source)
    g_source_destroy (client->timeout_source);

  /* A new timeout: */
  client->timeout_source =
    g_timeout_source_new (client->data->net_timeout * 1000);
  g_source_set_callback (client->timeout_source,
			 (GSourceFunc) network_client_timeout, client, NULL);
  g_source_attach (client->timeout_source, client->data->net_context);

  /* If the line is too long: */
  if (size > HTTP_MAX_LINE)
    {
      log_write_generic (client->data, LOG_VERBOSE_INFO,
			 LOG_NETWORK_CLIENT_ERROR, "client", LOG_VERBOSE_INFO,
			 LOG_TYPE_STRING, client->ip, "error",
			 LOG_VERBOSE_INFO, LOG_TYPE_STRING,
			 "Header line too long", NULL);

      network_client_error (client, HTTP_ERROR_400);
      g_free (line);
      return FALSE;
    }

  line[last] = 0;

  /* Empty line means the data block: */
  if (line[0] == 0)
    {
      TBHttpError error;

      g_free (line);

      if (network_client_header (client, &error) == FALSE)
	{
	  network_client_error (client, error);
	  return FALSE;
	}

      network_client_switch (client);
      return FALSE;
    }

  else if (client->header)
    g_free (line);

  else
    client->header = line;

  return TRUE;
}

static gboolean
network_client_read_timeout (TBNetworkClient * client)
{
  g_source_destroy (client->channel_source);

  client->channel_source = g_io_create_watch (client->channel, G_IO_IN);
  g_source_set_callback (client->channel_source,
			 (GSourceFunc) network_client_read, client, NULL);
  g_source_attach (client->channel_source, client->data->net_context);
  return FALSE;
}

/* CLIENT WRITE *************************************************************/

/* This function writes something to the client: */
static gboolean
network_client_write (GIOChannel * source, GIOCondition cond,
		      TBNetworkClient * client)
{
  gsize done;
  GIOStatus status;

  if ((status =
       g_io_channel_write_chars (source, client->output + client->output_done,
				 client->output_size - client->output_done,
				 &done, NULL)) == G_IO_STATUS_NORMAL)
    status = g_io_channel_flush (client->channel, NULL);

  /* The status of the read: */
  switch (status)
    {
    case G_IO_STATUS_NORMAL:
      client->output_done += done;

      if (client->output_done >= client->output_size)
	{
	  network_client_close (client);
	  return FALSE;
	}

      break;

      /* Setting a delay: */
    case G_IO_STATUS_AGAIN:
      g_source_destroy (client->channel_source);

      client->channel_source = g_timeout_source_new (200);
      g_source_set_callback (client->channel_source,
			     (GSourceFunc) network_client_write_timeout,
			     client, NULL);
      g_source_attach (client->channel_source, client->data->net_context);
      return FALSE;

      /* Close the socket: */
    case G_IO_STATUS_ERROR:
    case G_IO_STATUS_EOF:
      network_client_close (client);
      return FALSE;
    }

  /* Removing the timeout: */
  if (client->timeout_source)
    g_source_destroy (client->timeout_source);

  /* A new timeout: */
  client->timeout_source =
    g_timeout_source_new (client->data->net_timeout * 1000);
  g_source_set_callback (client->timeout_source,
			 (GSourceFunc) network_client_timeout, client, NULL);
  g_source_attach (client->timeout_source, client->data->net_context);

  return TRUE;
}

static gboolean
network_client_write_timeout (TBNetworkClient * client)
{
  g_source_destroy (client->channel_source);

  client->channel_source = g_io_create_watch (client->channel, G_IO_OUT);
  g_source_set_callback (client->channel_source,
			 (GSourceFunc) network_client_write, client, NULL);
  g_source_attach (client->channel_source, client->data->net_context);
  return FALSE;
}

/* Error message: */
static void
network_client_error (TBNetworkClient * client, TBHttpError error)
{
  gchar *buffer;
  gchar *header;

  switch (error)
    {
    case HTTP_ERROR_400:
      header = "HTTP/1.1 " HTTP_ERROR_400_TITLE;
      buffer =
	g_strdup_printf (HTTP_ERROR_BODY, HTTP_ERROR_400_TITLE,
			 HTTP_ERROR_400_MESSAGE);
      break;

    case HTTP_ERROR_404:
      header = "HTTP/1.1 " HTTP_ERROR_404_TITLE;
      buffer =
	g_strdup_printf (HTTP_ERROR_BODY, HTTP_ERROR_404_TITLE,
			 HTTP_ERROR_404_MESSAGE);
      break;

    default:
      return;
    }

  network_client_send (client, header, buffer, strlen (buffer));
  g_free (buffer);
}

/* This function writes to the socket: */
static gboolean
network_client_send (TBNetworkClient * client, gchar * header, gchar * buffer,
		     gsize buffersize)
{
  GString *str;

  str = g_string_new (NULL);

  /* Header: */
  str = g_string_append (str, header);

  /* End of the string: */
  str = g_string_append (str, "\r\n");

  /* the server: */
  str = g_string_append (str, "Server: " PACKAGE " " VERSION "\r\n");

  /* Body length: */
  g_string_append_printf (str, "Content-Length: %" G_GSIZE_FORMAT "\r\n",
			  buffersize + 2);

  /* Body type: */
  g_string_append_printf (str,
			  "Content-Type: " HTTP_MIME_XML
			  "; charset=UTF-8\r\n");

  /* Empty line: */
  str = g_string_append (str, "\r\n");

  /* The body: */
  str = g_string_append (str, buffer);

  /* Empty line: */
  str = g_string_append (str, "\r\n");

  client->output_size = str->len;
  client->output = g_string_free (str, FALSE);

  network_client_switch (client);
  return TRUE;
}

/* This function removes the read source and creates the write source: */
static void
network_client_switch (TBNetworkClient * client)
{
  /* Creating the writing function: */
  g_source_destroy (client->channel_source);

  client->channel_source = g_io_create_watch (client->channel, G_IO_OUT);
  g_source_set_callback (client->channel_source,
			 (GSourceFunc) network_client_write, client, NULL);
  g_source_attach (client->channel_source, client->data->net_context);
}

/* CLIENT HEADER ************************************************************/
static gchar *network_client_header_parse (gchar * request,
					   gchar *** subparts,
					   GList ** keyvalue);
static gchar *network_client_header_escape (gchar * string);

/* Header parser: */
static gboolean
network_client_header (TBNetworkClient * client, TBHttpError * error)
{
  GString *result = NULL;
  GList *keyvalue = NULL;
  gchar **subparts = NULL;
  gchar *request;
  gchar **parts;
  gint i;

  if (!client->header)
    {
      *error = HTTP_ERROR_400;
      return FALSE;
    }

  parts = g_strsplit (client->header, " ", -1);

  if (!parts || !parts[0] || !parts[1])
    {
      *error = HTTP_ERROR_400;

      if (parts)
	g_strfreev (parts);

      return FALSE;
    }

  /* Only GET requests: */
  if (strcmp (parts[0], HTTP_GET))
    {
      *error = HTTP_ERROR_400;
      g_strfreev (parts);
      return FALSE;
    }

  request = network_client_header_parse (parts[1], &subparts, &keyvalue);

  /* Looking for the request: */
  for (i = 0; tb_http_requests[i].request; i++)
    {
      if (!strcmp (tb_http_requests[i].request, request))
	{
	  result =
	    tb_http_requests[i].func (client->data, subparts, keyvalue);
	  break;
	}
    }

  g_free (request);

  if (subparts)
    g_strfreev (subparts);

  if (keyvalue)
    {
      g_list_foreach (keyvalue, (GFunc) tb_keyvalue_destroy, NULL);
      g_list_free (keyvalue);
    }

  if (!result)
    {
      *error = HTTP_ERROR_400;
      g_strfreev (parts);
      return FALSE;
    }

  network_client_send (client, "HTTP/1.1 " HTTP_ERROR_200_TITLE, result->str,
		       result->len);

  g_strfreev (parts);
  return TRUE;
}

/* Parsing of the http request: */
static gchar *
network_client_header_parse (gchar * request, gchar *** ret_subparts,
			     GList ** keyvalue)
{
  gchar **subparts;
  gchar **split;
  gchar **attr;
  gchar *path;
  gint i;

  GList *ret = NULL;

  *keyvalue = NULL;
  *ret_subparts = NULL;

  if (!(split = g_strsplit (request, "?", 2)) || !split[0]
      || split[0][0] != '/')
    {
      if (split)
	g_strfreev (split);

      return g_strdup (request);
    }

  path = split[0];

  while (path[0] == '/')
    path++;

  if (!(subparts = g_strsplit (path, "/", -1)) || !subparts[0])
    {
      request = network_client_header_escape (path);

      if (subparts)
	g_strfreev (subparts);
    }

  else
    {
      request = network_client_header_escape (subparts[0]);

      for (i = 1; subparts[i]; i++)
	{
	  gchar *tmp;

	  tmp = network_client_header_escape (subparts[i]);
	  g_free (subparts[i]);
	  subparts[i] = tmp;
	}

      *ret_subparts = subparts;
    }

  if (!split[1] || !(attr = g_strsplit (split[1], "&", -1)))
    {
      g_strfreev (split);
      return request;
    }

  for (i = 0; attr[i]; i++)
    {
      gchar **attribute;
      gchar *key, *value;

      if (!(attribute = g_strsplit (attr[i], "=", 2)) || !attribute[0]
	  || !attribute[1])
	{
	  if (attribute)
	    g_strfreev (attribute);

	  continue;
	}

      key = network_client_header_escape (attribute[0]);
      value = network_client_header_escape (attribute[1]);

      ret = g_list_append (ret, tb_keyvalue_new (key, value));

      g_free (value);
      g_free (key);
      g_strfreev (attribute);
    }

  g_strfreev (attr);
  g_strfreev (split);

  *keyvalue = ret;
  return request;
}

/* URL decode: */
static gchar *
network_client_header_escape (gchar * string)
{
  gint i, j, len;
  gchar *ret;

  len = strlen (string);

  ret = g_malloc (sizeof (gchar) * (len + 1));

  for (i = j = 0; i < len; i++)
    {
      if (string[i] == '%' && i < len - 2)
	{
	  gchar byte;

	  byte = string[i + 1] * 16 + string[i + 2];
	  ret[j++] = byte;
	  i += 2;
	}

      else
	ret[j++] = string[i];
    }

  ret[j] = 0;
  return ret;
}

/* CLIENT CLOSE *************************************************************/
static void
network_client_close (TBNetworkClient * client)
{
  client->data->net_clients =
    g_list_remove (client->data->net_clients, client);
  client->data->net_clients_numb--;

  network_client_free (client);
}

static void
network_client_free (TBNetworkClient * client)
{
  if (!client)
    return;

  if (client->ip)
    g_free (client->ip);

  if (client->channel)
    {
      g_io_channel_shutdown (client->channel, FALSE, NULL);
      g_io_channel_unref (client->channel);
    }

  if (client->channel_source)
    g_source_destroy (client->channel_source);

  if (client->timeout_source)
    g_source_destroy (client->timeout_source);

  if (client->header)
    g_free (client->header);

  if (client->output)
    g_free (client->output);

  g_free (client);
}

/* EOF */
